using System.Diagnostics.CodeAnalysis;

namespace Bread.Utility;

public struct RectN : IEquatable<RectN>, IParsable<RectN>
{
    /// <summary>
    /// (0, 0, 0, 0)
    /// </summary>
    public static RectN Empty => new RectN();

    /// <summary>
    /// (0, 0, 1, 1)
    /// </summary>
    public static RectN Identity => new RectN(0, 0, 1, 1);

    public int X { get; set; } = 0;

    public int Y { get; set; } = 0;

    public int Width { get; set; } = 0;

    public int Height { get; set; } = 0;

    public int Top => Y;

    public int Left => X;

    public int Right => X + Width;

    public int Bottom => Y + Height;

    public int W => Width;

    public int H => Height;

    public SizeN Size => new SizeN(Width, Height);

    public bool IsEmpty => Width == 0 || Height == 0;

    public RectN()
    {
    }

    public RectN(int x, int y, int width, int height)
    {
        X = x;
        Y = y;
        Width = width;
        Height = height;
    }

    /// <summary>
    /// return (0, 0, 1, 1) if invalid, otherwise return itself
    /// </summary>
    /// <returns></returns>
    public RectN IdentityIfInvalid()
    {
        if (Width <= 0) return new RectN(0, 0, 1, 1);
        if (Height <= 0) return new RectN(0, 0, 1, 1);
        return this;
    }

    public bool Contains(int x, int y)
    {
        if (x < X) return false;
        if (x > X + Width) return false;
        if (y < Y) return false;
        if (y > Y + Height) return false;
        return true;
    }

    public RectN Clip(int dstW, int dstH)
    {
        var l = X < 0 ? 0 : X;
        var r = X + Width > dstW ? dstW : X + Width;
        var t = Y < 0 ? 0 : Y;
        var b = Y + Height > dstH ? dstH : Y + Height;
        return new RectN(l, t, r - l, b - t);
    }


    public RectN Clone()
    {
        return new RectN(X, Y, Width, Height);
    }

    public RectD ToRectD()
    {
        return new RectD(X, Y, Width, Height);
    }

    /// <summary>
    /// 返回子区域
    /// </summary>
    /// <param name="rc">无量纲归一化位置</param>
    /// <returns></returns>
    public RectN Sub(RectD rc)
    {
        return new RectN((int)(X + rc.X * Width), (int)(Y + rc.Y * Height)
            , (int)(Width * rc.Width), (int)(Height * rc.Height));
    }

    public bool Contains(PointN pt)
    {
        return Contains(pt.X, pt.Y);
    }

    public void Inflate(int width, int height)
    {
        this.X -= width;
        this.Y -= height;
        this.Width += 2 * width;
        this.Height += 2 * height;
    }

    public RectN Intersect(RectN rc)
    {
        int x1 = Math.Max(X, rc.X);
        int x2 = Math.Min(Right, rc.Right);
        int y1 = Math.Max(Y, rc.Y);
        int y2 = Math.Min(Bottom, rc.Bottom);

        if (x2 > x1 && y2 > y1) {
            return new RectN(x1, y1, x2 - x1, y2 - y1);
        }
        return Empty;
    }

    public bool IsIntersectWith(RectN rect)
    {
        return (rect.X < Right) && (this.X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom);
    }

    public RectD IntersectThenNormalize(int dstW, int dstH)
    {
        var l = X < 0 ? 0 : X;
        var r = X + Width > dstW ? dstW : X + Width;
        var t = Y < 0 ? 0 : Y;
        var b = Y + Height > dstH ? dstH : Y + Height;
        return new RectD((l - X) / (double)Width, (t - Y) / (double)Height,
            (r - l) / (double)Width, (b - t) / (double)Height);
    }

    public RectN Union(RectN rect)
    {
        if (Width == 0 && Height == 0) {
            return rect;
        }
        else if (rect.Width == 0 && rect.Height == 0) {
            return this;
        }
        else {
            var x1 = Math.Min(X, rect.X);
            var x2 = Math.Max(Right, rect.Right);
            var y1 = Math.Min(Y, rect.Y);
            var y2 = Math.Max(Bottom, rect.Bottom);
            return new RectN(x1, y1, x2 - x1, y2 - y1);
        }
    }

    internal static RectN? Union(RectN? left, RectN? right)
    {
        if (left == null)
            return right;
        if (right == null)
            return left;
        return left.Value.Union(right.Value);
    }

    public void Offset(int x, int y)
    {
        this.X += x;
        this.Y += y;
    }

    public RectN Translate(int x, int y)
    {
        return new RectN(X + x, Y + y, Width, Height);
    }

    public static RectN Inflate(RectN rect, int x, int y)
    {
        RectN r = rect;
        r.Inflate(x, y);
        return r;
    }

    public RectD Normalize(double w, double h)
    {
        return new RectD(X / w, Y / h, Width / w, Height / h);
    }

    public RectN UnNormalize(double w, double h)
    {
        return new RectN((int)(X * w), (int)(Y * h), (int)(Width * w), (int)(Height * h));
    }

    public bool Equals(RectN o)
    {
        if (X != o.X) return false;
        if (Y != o.Y) return false;
        if (Width != o.Width) return false;
        if (Height != o.Height) return false;
        return true;
    }

    public override bool Equals(object? obj) => obj is RectN other && Equals(other);

    public override int GetHashCode()
    {
        unchecked {
            int hash = 17;
            hash = (hash * 23) + X.GetHashCode();
            hash = (hash * 23) + Y.GetHashCode();
            hash = (hash * 23) + Width.GetHashCode();
            hash = (hash * 23) + Height.GetHashCode();
            return hash;
        }
    }

    public override string ToString()
    {
        return $"({X},{Y},{W},{H})";
    }

    public static RectN Parse(string s, IFormatProvider? provider)
    {
        _ = TryParse(s, provider, out var result);
        return result;
    }

    public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out RectN result)
    {
        result = Empty;

        if (s == null) return false;
        if (s.StartsWith('(') == false) return false;
        if (s.EndsWith(')') == false) return false;
        if (s.Length < 9) return false;
        var splits = s.Substring(1, s.Length - 2).Split(',');
        if (splits.Length != 4) return false;

        if (int.TryParse(splits[0], out var x) == false) return false;
        if (int.TryParse(splits[1], out var y) == false) return false;
        if (int.TryParse(splits[2], out var w) == false) return false;
        if (int.TryParse(splits[3], out var h) == false) return false;

        result.X = x;
        result.Y = y;
        result.Width = w;
        result.Height = h;
        return true;
    }

    #region Operater

    public static bool operator ==(RectN left, RectN right)
    {
        return left.Equals(right);
    }

    public static bool operator !=(RectN left, RectN right)
    {
        return !(left == right);
    }

    public static RectD operator *(RectN rect, double scale)
    {
        return new RectD(
            rect.X * scale,
            rect.Y * scale,
            rect.Width * scale,
            rect.Height * scale);
    }

    public static RectN operator *(RectN rect, int scale)
    {
        return new RectN(
            rect.X * scale,
            rect.Y * scale,
            rect.Width * scale,
            rect.Height * scale);
    }

    #endregion
}